package edu.columbia.cloudbox.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import android.content.Context;
import android.content.ContextWrapper;
import android.util.Log;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;

public class S3IO {
	public static InputStream CREDENTIALS_INPUT_STREAM;

	private String bucketName = "hw2-s3-bucket";
	private AWSCredentials credentials;
	private AmazonS3Client s3;
	private Context context;

	private static String className = S3IO.class.getSimpleName();

	/**
	 * Initialize with Amazon s3 object
	 * 
	 * @param s3
	 *            Amazon s3 object
	 **/
	public S3IO(AmazonS3Client s3, Context cw) {
		final String methodName = "Constructor";
		Log.i(className, methodName + ": Entering...");
		this.s3 = s3;
		this.context = cw;
	}

	public S3IO(InputStream is, ContextWrapper cw) {
		final String methodName = "Constructor";
		Log.i(className, methodName + ": Entering...");

		this.context = cw;
		this.credentials = new PropertiesCredentials(is);
		s3 = new AmazonS3Client(credentials);

	}

	/**
	 * Initialize with .properties file path
	 * 
	 * @param propertiesPath
	 *            .properties file path
	 **/
	public S3IO(String propertiesPath, ContextWrapper cw) {
		final String methodName = "Constructor";
		Log.i(className, methodName + ": Entering...");

		this.context = cw;
		try {
			this.credentials = new PropertiesCredentials(new FileInputStream(
					propertiesPath));
			s3 = new AmazonS3Client(credentials);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Upload file to the same file path as local directory on server. If a file
	 * with the same file path exists on server, replace it with the new file.
	 * 
	 * @param localFilePath
	 *            local file path
	 * @throws FileNotFoundException
	 */
	public boolean upload(String localFilePath) throws FileNotFoundException {
		return this.uploadTo(localFilePath, localFilePath);
	}

	/**
	 * Upload file to a different file path on server. If a file with the same
	 * file path exists on server, replace it with the new file.
	 * 
	 * @param localFilePath
	 *            file path on local storage
	 * @param serverFilePath
	 *            file path on server
	 * @param bucketName
	 *            bucket name
	 * @throws FileNotFoundException
	 */
	public boolean uploadTo(String localFilePath, String serverFilePath)
			throws FileNotFoundException {

		final String methodName = "uploadTo";
		Log.i(className, methodName + ": Entering...");

		Log.i(className, methodName + ": Checking file existance...");
		File appDir = context.getFilesDir();
		File localFile = new File(appDir, localFilePath);
		if (!localFile.exists()) {
			throw new FileNotFoundException();
		}

		// s3.deleteObject(bucketName, serverFilePath);

		Log.i(className, methodName + ": Uploading file...");
		String key = serverFilePath;
		PutObjectRequest request = new PutObjectRequest(bucketName, key,
				localFile);
		s3.putObject(request);

		Log.i(className, methodName + ": Uploaded successfully!");
		return true;
	}

	/**
	 * Download file to local with the same file path on server. If the file
	 * already exists, update the file.
	 * 
	 * @param serverFilePath
	 *            file path on server
	 * @param bucketName
	 *            bucket name
	 * @throws S3ObjectNotFoundException
	 */
	public boolean download(String serverFilePath) throws IOException,
			S3ObjectNotFoundException {
		return this.downloadTo(serverFilePath, serverFilePath);
	}

	/**
	 * Download file to local with the a different file path to server file
	 * path. If the file already exists, update the file.
	 * 
	 * @param serverFilePath
	 *            file path on server
	 * @param localFilePath
	 *            local file path
	 * @param bucketName
	 *            bucket name
	 * @throws IOException
	 * @throws S3ObjectNotFoundException
	 */
	public boolean downloadTo(String serverFilePath, String localFilePath)
			throws IOException, S3ObjectNotFoundException {

		final String methodName = "downloadTo";

		Log.i(className, methodName + ": Entering...");

		Log.i(className, methodName + ": Downloading file...");
		String key = serverFilePath;
		S3Object object = s3.getObject(new GetObjectRequest(bucketName, key));
		if (object == null) {
			throw new S3ObjectNotFoundException();
		}
		InputStream is = object.getObjectContent();

		// Creating folder on local storage
		Log.i(className, methodName + ": Creating directory...");
		File localFile = new File(localFilePath);
		File appDir = context.getFilesDir();
		File localDir = new File(appDir, localFile.getParent());
		localDir.mkdirs();

		// Creating file
		Log.i(className, methodName + ": Creating file...");
		File file = new File(localDir.getPath(), localFile.getName());
		FileOutputStream fos = new FileOutputStream(file);

		int read = -1;

		while ((read = is.read()) != -1) {
			fos.write(read);
		}

		fos.flush();
		fos.close();
		is.close();

		Log.i(className, methodName + ": Downloaded successfully!");
		return true;
	}

	/**
	 * Download files belong to a folder.
	 * 
	 * @throws S3ObjectNotFoundException
	 */
	public boolean downloadFolder(String folderName) throws IOException,
			S3ObjectNotFoundException {
		final String methodName = "downloadFolder(" + folderName + ")";
		boolean result = true;
		Log.i(className, methodName + ": Entering...");

		List<String> keys = getKeysByExt(folderName, "*");
		Log.i(className + "." + methodName,
				"Need to download: " + keys.toString());

		for (String key : keys) {
			result = result && this.download(key);
		}

		return result;
	}

	/**
	 * Download files belong to a folder and has a specific extension name.
	 * 
	 * @throws S3ObjectNotFoundException
	 */
	public boolean downloadFolderByExt(String folderName, String extensionName)
			throws IOException, S3ObjectNotFoundException {
		final String methodName = "downloadFolderByExt(" + folderName + ","
				+ extensionName + ")";
		boolean result = true;
		Log.i(className, methodName + ": Entering...");

		List<String> keys = getKeysByExt(folderName, extensionName);
		Log.i(className + "." + methodName,
				"Need to download: " + keys.toString());
		for (String key : keys) {
			result = result && this.download(key);
		}

		return result;
	}

	/**
	 * Download files belong to a folder and has specific extension names.
	 * 
	 * @throws S3ObjectNotFoundException
	 */
	public boolean downloadFolderByExts(String folderName,
			List<String> extensionNames) throws IOException,
			S3ObjectNotFoundException {
		final String methodName = "downloadFolderByExts(" + folderName + ","
				+ extensionNames + ")";
		boolean result = true;
		Log.i(className, methodName + ": Entering...");

		for (String extensionName : extensionNames) {
			result = result
					& this.downloadFolderByExt(folderName, extensionName);
		}

		return result;
	}

	/**
	 * Delete a file on server
	 * 
	 * @param serverFilePath
	 *            file path on server
	 * @param bucketName
	 *            bucket name
	 */
	public void deleteServerFile(String serverFilePath) {
		final String methodName = "deleteServerFile";
		Log.i(className, methodName + ": Entering...");
		Log.i(className, methodName + ": Deleting file...");

		s3.deleteObject(bucketName, serverFilePath);

		Log.i(className, methodName + ": Deleted successfully...");
	}

	public AmazonS3Client getS3() {
		return s3;
	}

	public void setS3(AmazonS3Client s3) {
		this.s3 = s3;
	}

	/**
	 * Get keys of files own by folderName and extensionName. (No slash before
	 * or after path)
	 * 
	 * @throws S3ObjectNotFoundException
	 */
	public List<String> getKeysByExt(String folderName, String extensionName) {
		final String methodName = "getKeysByExt(" + folderName + ", "
				+ extensionName + ")";
		Log.i(className, methodName + ": Entering...");

		// Check user
		@SuppressWarnings("unchecked")
		List<S3ObjectSummary> objectList = s3.listObjects(bucketName,
				folderName).getObjectSummaries();

		List<String> resultList = new Vector<String>();
		for (S3ObjectSummary object : objectList) {
			String key = object.getKey();
			int idx = key.lastIndexOf(".");
			String ext = (idx == -1) ? "" : key.substring(idx + 1);
			/* Check if it is an folder and check extension */
			if (key.charAt(key.length() - 1) != '/'
					&& (extensionName.equals("*") || ext.equals(extensionName))) {
				resultList.add(object.getKey());
			}
		}

		Log.i(className, methodName + ": Return: " + resultList);
		return resultList;
	}

	/**
	 * Get keys of files own by folderName and extensionName. (No slash before
	 * or after path)
	 * 
	 * @throws S3ObjectNotFoundException
	 */
	public List<String> getKeysByExts(String folderName, List<String> exts) {
		final String methodName = "getKeysByExts(" + folderName + ", " + exts
				+ ")";
		Log.i(className, methodName + ": Entering...");

		List<String> resultList = new Vector<String>();

		for (String ext : exts) {
			resultList.addAll(this.getKeysByExt(folderName, ext));
		}

		Log.i(className, methodName + ": Return: " + resultList);
		return resultList;
	}

	/**
	 * Get all FileType that included by a folder
	 * 
	 */

	public List<FileType> getAllFileTypesByFolder(String folderName) {
		final String methodName = "getAllFileTypesByFolder(" + folderName + ")";
		Log.i(className, methodName + ": Entering...");

		if (folderName == null || folderName.equals("")) {
			return null;
		}

		List<FileType> resultList = new Vector<FileType>();

		@SuppressWarnings("unchecked")
		List<S3ObjectSummary> objectList = s3.listObjects(bucketName,
				folderName).getObjectSummaries();

		for (Object object : objectList) {
			S3ObjectSummary summary = (S3ObjectSummary) object;
			long size = summary.getSize();
			String key = summary.getKey();
			int slashIdx = key.lastIndexOf("/");
			String fileName = key.substring(slashIdx + 1);

			int dotIdx = fileName.lastIndexOf(".");
			String ext = (dotIdx == -1) ? "" : fileName.substring(dotIdx + 1);
			resultList.add(new FileType(fileName, false, ext, key, size));
		}

		Log.i(className, methodName + ": Return: " + resultList);
		return resultList;
	}

	/**
	 * Get display FileType views for a folder
	 * 
	 */

	public List<FileType> getFileTypesByFolder(String folderName) {
		String methodName = "getFileTypesByFolder";
		Log.i(className, methodName + ": Entering...");

		@SuppressWarnings("unchecked")
		List<S3ObjectSummary> objectList = s3.listObjects(bucketName,
				folderName).getObjectSummaries();

		List<FileType> resultList = new Vector<FileType>();

		for (S3ObjectSummary object : objectList) {
			String key = object.getKey();
			long size = object.getSize();
			if (key.length() == folderName.length() + 1) {
				continue;
			}

			String subKey = key.substring(folderName.length() + 1);
			int slashIdx = subKey.indexOf("/");

			// If it is file
			if (slashIdx == -1) {
				int dotIdx = subKey.lastIndexOf(".");
				String ext = (dotIdx == -1) ? "" : subKey.substring(dotIdx + 1);
				resultList.add(new FileType(subKey, false, ext, key, size));
			} else {
				FileType fileType = new FileType(subKey.substring(0, slashIdx),
						true, "folder", key);
				if (!resultList.contains(fileType)) {
					resultList.add(fileType);
				}
			}
		}

		Collections.sort(resultList);

		Log.i(className, methodName + ": Return: " + resultList);
		return resultList;
	}

	/**
	 * Get extension names within a folder
	 */
	public List<String> getExts(String folderName) {
		final String methodName = "getExts(" + folderName + ")";
		Log.i(className, methodName + ": Entering...");
		List<String> resultList = new Vector<String>();
		List<String> keys = getKeysByExt(folderName, "*");
		for (String key : keys) {
			int dotIdx = key.lastIndexOf(".");
			String ext = (dotIdx == -1) ? "" : key.substring(dotIdx + 1);
			if (!resultList.contains(ext)) {
				Log.i(className, methodName + ": Found new extension name: "
						+ ext);
				resultList.add(ext);
			}
		}

		Log.i(className, methodName + ": Return: " + resultList);
		return resultList;
	}
}
